/*
 * PCryptUtil.cpp
 *
 *  Created on: May 13, 2014
 *      Author: yuliang
 */

#include "PCryptUtil.h"
#include <openssl/md5.h>
#include <openssl/sha.h>
#include <openssl/des.h>
#include <string.h>
#include <assert.h>

#include "PLog.h"
#define LOG_TAG "cryptutil"

static unsigned char ToHex(unsigned char x);
static unsigned char FromHex(unsigned char x);

static const char b64_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

static const char reverse_table[128] = { 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
		64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63,
		52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64, 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
		13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64, 64, 26, 27, 28, 29, 30, 31, 32, 33, 34,
		35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64 };

bool CCryptUtil::m_bInit = false;

std::string CCryptUtil::UrlEncode(const std::string& str) {
	std::string strTemp("");
	size_t length = str.length();
	for (size_t i = 0; i < length; i++) {
		if (isalnum((unsigned char) str[i]) || (str[i] == '-') || (str[i] == '_') || (str[i] == '.')
				|| (str[i] == '~')) {
			//A-Za-z0-9-_.~
			strTemp += str[i];
		} else if (str[i] == ' ') {
			//空格
			strTemp += "+";
		} else {
			strTemp += '%';
			strTemp += ToHex((unsigned char) str[i] >> 4);
			strTemp += ToHex((unsigned char) str[i] % 16);
		}
	}
	MLOG_TRACE(LOG_TAG, "UrlEncode("<<str<<"): "<<strTemp);
	return strTemp;
}

std::string CCryptUtil::UrlDecode(const std::string& str) {
	std::string strTemp("");
	size_t length = str.length();
	for (size_t i = 0; i < length; i++) {
		if (str[i] == '+') {
			strTemp += ' ';
		} else if (str[i] == '%') {
			assert(i + 2 < length);
			if (i + 2 < length) {
				unsigned char high = FromHex((unsigned char) str[++i]);
				unsigned char low = FromHex((unsigned char) str[++i]);
				strTemp += high * 16 + low;
			} else {
				MLOG_ERROR(LOG_TAG, "UrlDecode("<<str<<"): len error!");
				strTemp = "";
				break;
			}
		} else {
			strTemp += str[i];
		}
	}
	return strTemp;
}

std::string CCryptUtil::Md5(const std::string& str, bool bUpper) {
	MD5_CTX ctx;
	MD5_Init(&ctx);
	MD5_Update(&ctx, str.c_str(), str.length());
	unsigned char md5[MD5_DIGEST_LENGTH] = { 0 };
	MD5_Final(md5, &ctx);
	char result[33] = { 0 };
	if (bUpper) {
		sprintf(result, "%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", md5[0], md5[1], md5[2],
				md5[3], md5[4], md5[5], md5[6], md5[7], md5[8], md5[9], md5[10], md5[11], md5[12], md5[13], md5[14],
				md5[15]);
	} else {
		sprintf(result, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", md5[0], md5[1], md5[2],
				md5[3], md5[4], md5[5], md5[6], md5[7], md5[8], md5[9], md5[10], md5[11], md5[12], md5[13], md5[14],
				md5[15]);
	}

	MLOG_TRACE(LOG_TAG, "Md5("<<str<<"): "<<result);
	return result;
}

std::string CCryptUtil::Base64Encode(const std::string& str) {
	using ::std::string;
	using ::std::numeric_limits;

	if (str.size() > (numeric_limits<string::size_type>::max() / 4u) * 3u) {
		MLOG_ERROR(LOG_TAG, "Converting too large a string to base64.");
		return "";
	}

	const ::std::size_t binlen = str.size();
	// Use = signs so the end is properly padded.
	string retval((((binlen + 2) / 3) * 4), '=');
	::std::size_t outpos = 0;
	int bits_collected = 0;
	unsigned int accumulator = 0;
	const string::const_iterator binend = str.end();

	for (string::const_iterator i = str.begin(); i != binend; ++i) {
		accumulator = (accumulator << 8) | (*i & 0xffu);
		bits_collected += 8;
		while (bits_collected >= 6) {
			bits_collected -= 6;
			retval[outpos++] = b64_table[(accumulator >> bits_collected) & 0x3fu];
		}
	}
	if (bits_collected > 0) { // Any trailing bits that are missing.
		assert(bits_collected < 6);
		if (bits_collected < 6) {
			accumulator <<= 6 - bits_collected;
			retval[outpos++] = b64_table[accumulator & 0x3fu];
		} else {
			MLOG_ERROR(LOG_TAG, "Base64Encode bits_collected >= 6");
			return "";
		}
	}
	assert(outpos >= (retval.size() - 2));
	assert(outpos <= retval.size());
	if (outpos < retval.size() - 2 || outpos > retval.size()) {
		MLOG_ERROR(LOG_TAG, "Base64Encode outpos error!");
		return "";
	}
	MLOG_TRACE(LOG_TAG, "Base64("<<str<<"): "<<retval);
	return retval;
}

std::string CCryptUtil::Base64Decode(const std::string& str) {
	using ::std::string;
	string retval;
	const string::const_iterator last = str.end();
	int bits_collected = 0;
	unsigned int accumulator = 0;

	for (string::const_iterator i = str.begin(); i != last; ++i) {
		const int c = *i;
		if (::std::isspace(c) || c == '=') {
			// Skip whitespace and padding. Be liberal in what you accept.
			continue;
		}
		if ((c > 127) || (c < 0) || (reverse_table[c] > 63)) {
			MLOG_ERROR(LOG_TAG, "This contains characters not legal in a base64 encoded string.");
			retval = "";
			break;
		}
		accumulator = (accumulator << 6) | reverse_table[c];
		bits_collected += 6;
		if (bits_collected >= 8) {
			bits_collected -= 8;
			retval += (char) ((accumulator >> bits_collected) & 0xffu);
		}
	}
	return retval;
}

std::string CCryptUtil::Sha1(const std::string& str, bool bUpper) {
	SHA_CTX ctx;
	SHA1_Init(&ctx);
	SHA1_Update(&ctx, str.c_str(), str.length());
	unsigned char sha[SHA_DIGEST_LENGTH] = { 0 };
	SHA1_Final(sha, &ctx);
	char result[40 + 1] = { 0 };
	if (bUpper) {
		sprintf(result, "%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", sha[0],
				sha[1], sha[2], sha[3], sha[4], sha[5], sha[6], sha[7], sha[8], sha[9], sha[10], sha[11], sha[12],
				sha[13], sha[14], sha[15], sha[16], sha[17], sha[18], sha[19]);
	} else {
		sprintf(result, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", sha[0],
				sha[1], sha[2], sha[3], sha[4], sha[5], sha[6], sha[7], sha[8], sha[9], sha[10], sha[11], sha[12],
				sha[13], sha[14], sha[15], sha[16], sha[17], sha[18], sha[19]);
	}
	MLOG_TRACE(LOG_TAG, "Sha1("<<str<<") = "<<result);
	return result;
}

std::string CCryptUtil::Sha256(const std::string& str, bool bUpper) {
	SHA256_CTX ctx;
	SHA256_Init(&ctx);
	SHA256_Update(&ctx, str.c_str(), str.length());
	unsigned char sha[SHA256_DIGEST_LENGTH] = { 0 };
	SHA256_Final(sha, &ctx);
	char result[64 + 1] = { 0 };
	if (bUpper) {
		sprintf(result,
				"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
				sha[0], sha[1], sha[2], sha[3], sha[4], sha[5], sha[6], sha[7], sha[8], sha[9], sha[10], sha[11],
				sha[12], sha[13], sha[14], sha[15], sha[16], sha[17], sha[18], sha[19], sha[20], sha[21], sha[22],
				sha[23], sha[24], sha[25], sha[26], sha[27], sha[28], sha[29], sha[30], sha[31]);
	} else {
		sprintf(result,
				"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
				sha[0], sha[1], sha[2], sha[3], sha[4], sha[5], sha[6], sha[7], sha[8], sha[9], sha[10], sha[11],
				sha[12], sha[13], sha[14], sha[15], sha[16], sha[17], sha[18], sha[19], sha[20], sha[21], sha[22],
				sha[23], sha[24], sha[25], sha[26], sha[27], sha[28], sha[29], sha[30], sha[31]);
	}
	MLOG_TRACE(LOG_TAG, "Sha256("<<str<<") = "<<result);
	return result;
}

std::string CCryptUtil::TripleDes(const std::string& str, const std::string& key, DesOperation op, DesMethod method,
		bool bUsingBase64) {
	const char *data = NULL;
	int len = 0;
	if (kDesOperationDecrypt == op && bUsingBase64) {
		//先解base64
		std::string strDecode = Base64Decode(str);
		if (strDecode.empty()) {
			MLOG_ERROR(LOG_TAG, "TripleDes解base64失败！");
			return "";
		}
		data = strDecode.c_str();
		len = strDecode.length();
	} else {
		data = str.c_str();
		len = str.length();
	}
	// key 24
	char deskey[25] = { 0 };
	strncpy(deskey, key.c_str(), 24);
	// 3 des_key
	DES_key_schedule k1, k2, k3;
	DES_cblock block = { 0 };
	memcpy(block, deskey, 8);
	DES_set_key_unchecked((const_DES_cblock*) block, &k1);
	memcpy(block, deskey + 8, 8);
	DES_set_key_unchecked((const_DES_cblock*) block, &k2);
	memcpy(block, deskey + 16, 8);
	DES_set_key_unchecked((const_DES_cblock*) block, &k3);
	// crypt every 8 bytes
	unsigned char out[8] = { 0 };
	unsigned char in[8] = { 0 };
	int count = len / 8;
	std::string strDes("");
	int i = 0;
	for (i = 0; i < count; ++i) {
		memcpy(in, data + i * 8, 8);
		DES_ecb3_encrypt((const_DES_cblock*) in, (DES_cblock*) out, &k1, &k2, &k3, op);
		strDes.append((char*) out, 8);
	}
	if (len % 8 != 0) {
		//
		memset(in, 0x00, 8);
		memset(out, 0x00, 8);
		memcpy(in, data + i * 8, len % 8);
		DES_ecb3_encrypt((const_DES_cblock*) in, (DES_cblock*) out, &k1, &k2, &k3, op);
		strDes.append((char*) out, 8);
	}
	if (kDesOperationEncrypt == op && bUsingBase64) {
		//base64输出
		strDes = Base64Encode(strDes);
	}
	return strDes;
}

int CCryptUtil::Random(int begin, int end) {
	if (!m_bInit) {
		srand(time(NULL));
		m_bInit = true;
	}
	return rand() % (end - begin + 1) + begin;
}

static unsigned char ToHex(unsigned char x) {
	return x > 9 ? x + 55 : x + 48;
}

static unsigned char FromHex(unsigned char x) {
	unsigned char y = 0;
	if (x >= 'A' && x <= 'Z') {
		y = x - 'A' + 10;
	} else if (x >= 'a' && x <= 'z') {
		y = x - 'a' + 10;
	} else if (x >= '0' && x <= '9') {
		y = x - '0';
	} else {
		MLOG_ERROR(LOG_TAG, "FromHex("<<x<<")");
		assert(0);
	}
	return y;
}

CCryptUtil::CCryptUtil() {
}

CCryptUtil::~CCryptUtil() {
}
